package com.lzj.server.common.service.impl;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.lang.Assert;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lzj.server.common.model.Attachment;
import com.lzj.server.common.model.FileInfo;
import com.lzj.server.common.service.AttachmentService;
import com.lzj.server.constant.RedisConstant;
import com.lzj.server.constant.SystemConstant;
import com.lzj.server.constant.properties.OssProperties;
import com.lzj.server.exceptions.BaseException;
import com.lzj.server.repository.mapper.AttachmentMapper;
import com.lzj.server.utils.FileUtils;
import com.lzj.server.utils.RedisUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Lzj
 * @date 2025/5/26
 * @description
 */
@Service
@Slf4j
public class AttachmentServiceImpl extends ServiceImpl<AttachmentMapper, Attachment> implements AttachmentService {

    @Resource
    private AttachmentMapper attachmentMapper;
    @Resource
    private AmazonS3 amazonS3;
    @Resource
    private OssProperties ossProperties;

    @Override
    public Boolean uploadPart(MultipartFile file, String uploadId, int partNumber) {
        String key = RedisConstant.FILE_PART_UPLOAD + uploadId;

        String finalPath = RedisUtils.getObjectBean(key, String.class);
        if (!RedisUtils.exist(key)) {
            throw new BaseException("上传失败，上传任务不存在或者失效");
        }

        String fileName = file.getOriginalFilename();
        Assert.isTrue(FileUtils.isValidFileType(fileName), "不支持的文件类型");

        try (InputStream inputStream = file.getInputStream()) {
            UploadPartRequest uploadRequest = new UploadPartRequest()
                    .withBucketName(ossProperties.getBucketName())
                    .withKey(finalPath)
                    .withUploadId(uploadId)
                    .withInputStream(inputStream)
                    .withPartNumber(partNumber)
                    .withPartSize(file.getSize());

            UploadPartResult uploadResult = amazonS3.uploadPart(uploadRequest);
            PartETag partETag = uploadResult.getPartETag();

            String etagKey = RedisConstant.MULTIPART_ETAG + uploadId;
            RedisUtils.zSet(etagKey, partETag, partNumber);
            RedisUtils.expire(etagKey, RedisConstant.MULTIPART_ETAG_EXPIRE_TIME);
            return true;
        } catch (IOException e) {
            throw new BaseException("上传失败", e);
        }
    }

    @Override
    public Long merge(FileInfo fileInfo) {
        String etagKey = RedisConstant.MULTIPART_ETAG + fileInfo.getUpLoadId();
        // 从 Redis ZSet 获取所有 PartETag，按 score（即 partNumber）排序
        Set<ZSetOperations.TypedTuple<Object>> tupleSet = RedisUtils.zRangeWithScores(etagKey, 0, -1);

        if (tupleSet == null || tupleSet.isEmpty()) {
            throw new BaseException("合并失败，没有找到分片");
        }

        List<PartETag> partETags = tupleSet.stream()
                .map(ZSetOperations.TypedTuple::getValue)
                .filter(Objects::nonNull)
                .map(obj -> (PartETag) obj)
                .collect(Collectors.toList());

        // 构造合并请求
        CompleteMultipartUploadRequest request = new CompleteMultipartUploadRequest(
                ossProperties.getBucketName(),
                fileInfo.getFinalPath(),
                fileInfo.getUpLoadId(),
                partETags
        );
        CompleteMultipartUploadResult result = amazonS3.completeMultipartUpload(request);

        // 清除 Redis 缓存
        RedisUtils.del(etagKey);
        // 保存附件信息
        Attachment attachment = new Attachment();
        String fileName = fileInfo.getFileName();
        attachment
                .setFileName(fileName)
                .setFileType(FileUtils.getFileTypeBySuffix(fileName))
                .setFileSize(fileInfo.getFileSize())
                .setPath(fileInfo.getFinalPath())
                .setFormat(FileUtils.getFileType(fileName))
                .setUploader(Long.parseLong(StpUtil.getLoginId().toString()));
        this.save(attachment);
        return attachment.getId();
    }

    @Override
    public String initUpload() {
        String finalPath = FileUtils
                .buildStoragePath(SystemConstant.VIDEO_MODULE, "temp", true);
        InitiateMultipartUploadRequest request = new InitiateMultipartUploadRequest(
                ossProperties.getBucketName(), finalPath);
        InitiateMultipartUploadResult result = amazonS3.initiateMultipartUpload(request);
        String uploadId = result.getUploadId();
        String key = RedisConstant.FILE_PART_UPLOAD + uploadId;
        // 默认五分钟
        RedisUtils.set(key, finalPath, RedisConstant.FILE_UPLOAD_TASK_TIMEOUT);
        return uploadId;
    }

    @Override
    public Long updateFile(MultipartFile file) {
        Assert.notNull(file, "文件不能为空");
        String fileName = file.getOriginalFilename();
        // 判断是否为支持类型
        Assert.isTrue(FileUtils.isValidFileType(fileName), "不支持的文件类型");
        String finalPath = FileUtils.buildStoragePath(SystemConstant.VIDEO_MODULE, fileName, true);
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(file.getContentType());
        objectMetadata.setContentLength(file.getSize());
        try {
            amazonS3.putObject(ossProperties.getBucketName(), finalPath, file.getInputStream(), objectMetadata);

            Attachment attachment = new Attachment();
            attachment
                    .setDuration(0)
                    .setFileName(fileName)
                    .setFileType(FileUtils.getFileTypeBySuffix(fileName))
                    .setFileSize(file.getSize())
                    .setPath(ossProperties.getBucketName() + "/" + finalPath)
                    .setFormat(FileUtils.getFileType(fileName))
                    .setUploader(Long.parseLong(StpUtil.getLoginId().toString()));

            attachmentMapper.insert(attachment);
            return attachment.getId();

        } catch (IOException e) {
            throw new BaseException("上传失败", e);
        }

    }
}

